﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Av.Utils;
using System.Threading;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace WebUtils
{
    public class SymfonyParser
    {
        /// <summary>
        /// Holds all I18N messages
        /// </summary>
        protected SymI18nMessageHolder m_i18nHolder = new SymI18nMessageHolder();

        /// <summary>
        /// Holds translations from I18N XML files
        /// </summary>
        Dictionary<string, Dictionary<string, string>> m_arTranslations = new Dictionary<string, Dictionary<string, string>>();

        /// <summary>
        /// Contains modules for all application
        /// </summary>
        protected Dictionary<string, List<string>> m_applicationModules = new Dictionary<string, List<string>>();

        /// <summary>
        /// Operation is finished, error, etc...
        /// </summary>
        public event OperationStatusHandler OperationStatusChanged = null;

        /// <summary>
        /// Indicates that new information for is appeared
        /// </summary>
        public event LogMessageHandler NewUserInfo = null;

        /// <summary>
        /// Fires event to indicate that operation status changed
        /// </summary>
        public void OnOperationStatusChanged(OperationStatusEventArgs.OperationType op, OperationStatusEventArgs.OperationStatusType status)
        {
            if( OperationStatusChanged != null )
            {
                OperationStatusChanged("SymfonyParser", new OperationStatusEventArgs(op, status));
            }
        }

        /// <summary>
        /// Fires NewUserInfo event to indicate that new message should be displayed
        /// </summary>
        public void OnNewUserInfo(string msg, params object[] args)
        {
            OnNewUserInfo(Importance.Info, msg, args);
        }

        /// <summary>
        /// Fires NewUserInfo event to indicate that new message should be displayed
        /// </summary>
        public void OnNewUserInfo(Importance level, string msg, params object[] args)
        {
            if( NewUserInfo != null )
            {
                NewUserInfo(level, msg, args);
            }
        }

        public string FirstAppName
        {
            get
            {
                if( m_applicationModules.Keys.Count > 0 )
                {
                    return m_applicationModules.Keys.ElementAt(0);
                }

                return "";
            }
        }

        public string CurrentCulture { get { return "lt"; } }

        /// <summary>
        /// Returns list of modules in all symfony applications
        /// </summary>
        public string[] GetModuleList(string appName)
        {
            return m_applicationModules.ContainsKey(appName) ? m_applicationModules[appName].ToArray() : null;
        }

        public void ParseSymfonyProject(object symfonyDirectory)
        {
            Log4cs.Log("Getting I18N messages from...");
            try
            {
                // PHP files to search for I18N
                List<string> arFiles = new List<string>();

                // I18N XML files
                List<string> arI18nFiles = new List<string>();

                // To list all directories w/o recursion
                Stack<string> dirs = new Stack<string>();
                dirs.Push((string)symfonyDirectory);

                string appName = string.Empty;

                while( dirs.Count > 0 )
                {
                    string currentDir = dirs.Pop();
                    //Log4cs.Log("Looking directories in {0}...", currentDir);

                    string[] arDirs = Directory.GetDirectories(currentDir);
                    for(int i = 0; (arDirs != null) && (i < arDirs.Length); i++)
                    {
                        DirectoryInfo di = new DirectoryInfo(arDirs[i]);

                        // Got new application
                        if( di.Parent.Name == "apps" )
                        {
                            appName = di.Name;
                        }

                        // Add directory if it is not SVN or hidden or symfony specific
                        if( !di.Name.StartsWith(".") && !di.FullName.Contains("data\\symfony\\") )
                        {
                            dirs.Push(arDirs[i]);
                            
                            // Add module to application if folder is located in module folder
                            if( (di.Parent.Name == "modules") && (!string.IsNullOrEmpty(appName)) )
                            {
                                if( !m_applicationModules.ContainsKey(appName))
                                {
                                    m_applicationModules[appName] = new List<string>();
                                }
                                m_applicationModules[appName].Add(di.Name);
                            }  // END IF ( this is module )
                        }
                    }

                    if(currentDir.EndsWith("i18n")) 
                    {
                        string[] files = Directory.GetFiles(currentDir, "*.xml");
                        arI18nFiles.AddRange(files);
                    } else if(currentDir.EndsWith("templates"))
                    {
                        string[] files = Directory.GetFiles(currentDir, "*.php");
                        arFiles.AddRange(files);
                    }

                }  // END WHILE (get all directories and PHP files)

                // Fire that project is parsed
                OnOperationStatusChanged(OperationStatusEventArgs.OperationType.ProjectParsing, OperationStatusEventArgs.OperationStatusType.Ok);

                OnNewUserInfo(Importance.Info, "Got {0} templates files...", arFiles.Count);
                //Form1.OnLogMessage(Importance.Info, "Got {0} I18N files...", arI18nFiles.Count);

                // Load all translations from XML files
                //LoadI18nTranslationsFromXml(arI18nFiles.ToArray());

                //Log4cs.Log("Got {0} PHP files to search", arFiles.Count);
                SearchFilesForI18nMessages(arFiles.ToArray());

            } catch(Exception ex)
            {
                Log4cs.Log(Importance.Error, "Error searching for I18N messages!");
                Log4cs.Log(Importance.Debug, ex.ToString());
                OnOperationStatusChanged(OperationStatusEventArgs.OperationType.ProjectParsing, OperationStatusEventArgs.OperationStatusType.Failed);
            }
            Log4cs.Log("I18N thread finished");
        }

        /// <summary>
        /// Loads all XML translation from list of XML files
        /// </summary>
        /// <param name="arI18nFiles"></param>
        private void LoadI18nTranslationsFromXml(string[] arI18nFiles)
        {
            try
            {
                foreach(string filename in arI18nFiles)
                {
                    DirectoryInfo di = new DirectoryInfo(filename);
                    string modulename = di.Parent.Parent.Name;
                    Log4cs.Log("Module of I18N file is: {0}...", modulename);
                    if( filename.EndsWith(".lt.xml") )
                    {
                        SymI18nXmlParser p = new SymI18nXmlParser();
                        p.Parse(filename);
                        m_arTranslations[modulename] = p.GetTranslations();
                    }
                }
            } catch(Exception ex)
            {
                Log4cs.Log(Importance.Error, "Error loading I18N translations!");
                Log4cs.Log(Importance.Debug, ex.ToString());
            }
        }

        private void SearchFilesForI18nMessages(string[] arFiles)
        {
            string s = "";
            try
            {
                Regex rx = new Regex("([\\s]*)(__)([\\s]*)(\\()()(\\s*)([\"']{1})", RegexOptions.IgnoreCase | RegexOptions.Multiline);
                foreach(string filename in arFiles)
                {
                    FileInfo fi = new FileInfo(filename);
                    //Log4cs.Log("Searching PHP file {0} to extract I18N messages...", filename);
                    s = File.ReadAllText(filename);
                    //Log4cs.Log(Importance.Debug, "File contains {0} bytes", s.Length);
                    Match m = rx.Match(s);
                    while(m.Success)
                    {
                        // Pass string to search and position of ' or "
                        string msg = GetEnquotedString(s, m.Index + m.Length - 1);
                        //Log4cs.Log("  {0}: {1}", fi.Name, msg);
                        m = m.NextMatch();

                        m_i18nHolder.Add(msg, "", "lt", filename, m.Index);
                    }
                }

                Log4cs.Log("Got {0} messages to be translated...", m_i18nHolder.Total);
                m_i18nHolder.Normalize();
            } catch(Exception ex)
            {
                Log4cs.Log(Importance.Error, "Error searching PHP files for I18N messages!");
                Log4cs.Log(Importance.Debug, ex.ToString());
            }
        }

        public void SaveAsXml(string filename)
        {
            string s = m_i18nHolder.GetAsXml();
            File.WriteAllText(filename, s);
        }

        public string GetEnquotedString(string s, int pos)
        {
            string v = "";

            char quote = s[pos];
            //Log4cs.Log("Searching string to be enclosed with: [{0}]", quote);
            pos++;
            // Whether there was backslash
            bool isEscaped = false;
            // Is string ends
            bool isFinished = false;
            while( !isFinished && (pos < s.Length) )
            {
                //Log4cs.Log("  :{0}{1}", s[pos], isEscaped ? " (escaped)" : "");
                if(s[pos] == quote)
                {
                    if(isEscaped)
                    {
                        v += s[pos];
                        isEscaped = false;
                    } else
                    {
                        isFinished = true;
                    }
                } else
                {
                    if( s[pos] == '\\' )
                    {
                        isEscaped = true;
                    }
                    v += s[pos];
                }

                pos++;
            }

            return v;
        }


        public string[] GetApplicationList()
        {
            return m_applicationModules.Keys.ToArray();
        }
    }
}
